/*
 * inotify-sync - a simple file synchronizer and file system watcher
 * Copyright (C) 2010-2011, inotify-sync developers and inotify-sync contributors
 * Copyright (C) 2010-2011, Cohesion Network Security Studio
 *
 * inotify-sync is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * inotify-sync is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with inotify-sync; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * FILE:			Inotify.cpp
 * USAGE:			---
 * DESCRIPTION:		---
 * OPTIONS:			---
 * REQUIREMENTS:	---
 * BUGS:			---
 * NOTES:			---
 * AUTHOR:			LI Yiwei (mail:leeyiw@gmail.com)
 * COMPANY:			---
 * VERSION:			1.0
 * CREATED:			Thu Sep  1 13:42:03 CST 2011
 * REVISION:		---
 */

#include "Inotify.h"

Inotify::Inotify(Profile& profile)
{
	this->profile=profile;

	if((fd = inotify_init()) == -1)
	{
		perror("inotify_init()");
		exit(errno);
	}

	mask = 0;
	if(profile.get_popt("create_action") != "")
	{
		mask |= IN_CREATE;
	}
	if(profile.get_popt("delete_action") != "")
	{
		mask |= IN_DELETE;
	}
	if(profile.get_popt("modify_action") != "")
	{
		mask |= IN_MODIFY;
	}
	if(profile.get_popt("moved_from_action") != "")
	{
		mask |= IN_MOVED_FROM;
	}
	if(profile.get_popt("moved_to_action") != "")
	{
		mask |= IN_MOVED_TO;
	}

	int errnum = 0;
	if((errnum = pthread_rwlock_init(&ievent_p_vec_lock,NULL)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_init(): %s\n",strerror(errnum));
		exit(errnum);
	}
	if((errnum = pthread_rwlock_init(&dnode_vec_lock,NULL)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_init(): %s\n",strerror(errnum));
		exit(errnum);
	}

	init_dir(profile.get_popt("watch_dir"));
}

Inotify::~Inotify()
{
	int errnum;
	if((errnum = pthread_rwlock_destroy(&ievent_p_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_destroy(): %s\n",strerror(errnum));
		exit(errnum);
	}
	if((errnum = pthread_rwlock_destroy(&dnode_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_destroy(): %s\n",strerror(errnum));
		exit(errnum);
	}

	if(-1 == close(fd))
	{
		perror("close()");
		exit(errno);
	}
}

void
Inotify::init_dir(const std::string root_dir)
{
	// 如果没有rx权限访问watch_dir，报错退出
	if(-1 == access(root_dir.c_str(), R_OK|X_OK))
	{
		perror("access()");
		exit(errno);
	}
	int errnum;
	if((errnum = pthread_rwlock_wrlock(&dnode_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_wrlock(): %s\n",strerror(errnum));
		exit(errnum);
	}
	
	nftw(root_dir.c_str(),
		do_init_dir,
		500,
		FTW_PHYS);

	if((errnum = pthread_rwlock_unlock(&dnode_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_unlock(): %s\n",strerror(errnum));
		exit(errnum);
	}
}

void
Inotify::destory_dir(const std::string root_dir)
{
	int errnum;
	if((errnum = pthread_rwlock_wrlock(&dnode_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_wrlock(): %s\n",strerror(errnum));
		exit(errnum);
	}

	std::vector<DirNode>::iterator iter;
	for(iter=dnode_vec.begin();iter<dnode_vec.end();iter++)
	{
		if((iter->abs_path).substr(0,root_dir.size())==root_dir)
			dnode_vec.erase(iter);
	}

	if((errnum = pthread_rwlock_unlock(&dnode_vec_lock)) != 0)
	{
		fprintf(stderr,"pthread_rwlock_wrlock(): %s\n",strerror(errnum));
		exit(errnum);
	}
}

void
Inotify::monitor()
{
	int errnum;

	//Create thread to run read_events and handle_events
	pthread_t handle_events_tid;
	if((errnum = pthread_create(&handle_events_tid,NULL,handle_events_wrapper,this)) != 0)
	{
		fprintf(stderr,"pthread_create(): %s\n",strerror(errnum));
		exit(errnum);
	}

	pthread_t read_events_tid;
	if((errnum = pthread_create(&read_events_tid,NULL,read_events_wrapper,this)) != 0)
	{
		fprintf(stderr,"pthread_create(): %s\n",strerror(errnum));
		exit(errnum);
	}

	//Join thread from read_events and handle_events
	void *read_events_ret;
	if((errnum = pthread_join(read_events_tid,&read_events_ret)) != 0)
	{
		fprintf(stderr,"pthread_join(): %s\n",strerror(errnum));
		exit(errnum);
	}
	void *handle_events_ret;
	if((errnum = pthread_join(handle_events_tid,&handle_events_ret)) != 0)
	{
		fprintf(stderr,"pthread_join(): %s\n",strerror(errnum));
		exit(errnum);
	}
}

void *
Inotify::read_events()
{
	int length=0;
	char buf[1000]={0};
	while((length=read(fd,buf,sizeof(buf)))!=-1)
	{
		int buf_offset=0;
		struct inotify_event *p_event=0,*event=0;
		while(buf_offset<length)
		{
			p_event=(struct inotify_event *)&buf[buf_offset];
			size_t event_size;
			event_size=offsetof(struct inotify_event,name)
							+p_event->len;
			event=(struct inotify_event *)malloc(event_size);
			memcpy(event,p_event,event_size);

//Lock ievent_p_vec and push_back event to it
			int errnum;
			if((errnum = pthread_rwlock_wrlock(&ievent_p_vec_lock)) != 0)
			{
				fprintf(stderr,"pthread_create(): %s\n",strerror(errnum));
				pthread_exit((void *)errnum);
			}
			ievent_p_vec.push_back(event);
			if((errnum = pthread_rwlock_unlock(&ievent_p_vec_lock)) != 0)
			{
				fprintf(stderr,"pthread_create(): %s\n",strerror(errnum));
				pthread_exit((void *)errnum);
			}

			buf_offset+=event_size;
		}
	}
	return ((void *)0);
}

void *
Inotify::handle_events()
{
	int errnum;
	while((errnum = pthread_rwlock_wrlock(&ievent_p_vec_lock)) == 0)
	{
		if(ievent_p_vec.size()>0)
		{
			p_event_handler->handle_events(&dnode_vec,
							&ievent_p_vec);
			for(size_t i=0;i<ievent_p_vec.size();i++)
				free(ievent_p_vec.at(i));
			ievent_p_vec.clear();
		}
		if((errnum = pthread_rwlock_unlock(&ievent_p_vec_lock)) != 0)
		{
			fprintf(stderr,"pthread_rwlock_unlock(): %s\n",strerror(errnum));
			pthread_exit((void *)errnum);
		}
	}
	if(errnum != 0)
	{
		fprintf(stderr,"pthread_rwlock_wrlock(): %s\n",strerror(errnum));
		pthread_exit((void *)errnum);
	}
	return ((void *)0);
}

void
Inotify::get_event_handler(EventHandler *p_event_handler)
{
	this->p_event_handler=p_event_handler;
}

void *
Inotify::read_events_wrapper(void *arg)
{
	Inotify *THIS = (Inotify *)arg;
	THIS->read_events();
	return NULL;
}

void *
Inotify::handle_events_wrapper(void *arg)
{
	Inotify *THIS = (Inotify *)arg;
	THIS->handle_events();
	return NULL;
}

int
do_init_dir(const char *fpath,
		const struct stat *sb,
		int typeflag,
		struct FTW *ftwbuf)
{
	if(typeflag==FTW_F)
		return 0;
	int wd=inotify_add_watch(fd,fpath,mask);
	if(wd==-1)
	{
		perror("inotify_add_watch()");
		exit(errno);
	}
	struct DirNode node={wd,fpath};
	dnode_vec.push_back(node);
	return 0;
}
